#include "WindowsPlatformMisc.h"

#include "WindowsApplication.h"
#include "Runtime/String/StringUtils.h"
namespace Alice
{
    WindowsGLRenderDevice WindowsGLRenderDevice::mCurrentOpenGLDevice;
    HINSTANCE WindowsPlatformMisc::mInstance=nullptr;
    static void QueryCpuInformation(FProcessorGroupDesc& OutGroupDesc, uint32& OutNumaNodeCount, uint32& OutCoreCount, uint32& OutLogicalProcessorCount, bool bForceSingleNumaNode = false)
	{
		GROUP_AFFINITY FilterGroupAffinity = {};

		if (bForceSingleNumaNode)
		{
			PROCESSOR_NUMBER ProcessorNumber = {};
			USHORT NodeNumber = 0;

			GetThreadIdealProcessorEx(GetCurrentThread(), &ProcessorNumber);
			GetNumaProcessorNodeEx(&ProcessorNumber, &NodeNumber);
			GetNumaNodeProcessorMaskEx(NodeNumber, &FilterGroupAffinity);
		}

		OutNumaNodeCount = OutCoreCount = OutLogicalProcessorCount = 0;
		uint8* BufferPtr = nullptr;
		DWORD BufferBytes = 0;

		if (false == GetLogicalProcessorInformationEx(RelationAll, (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) BufferPtr, &BufferBytes))
		{
			if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
			{
				BufferPtr = new uint8[BufferBytes];//reinterpret_cast<uint8*>(FMemory::Malloc(BufferBytes));

				if (GetLogicalProcessorInformationEx(RelationAll, (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) BufferPtr, &BufferBytes))
				{
					uint8* InfoPtr = BufferPtr;

					while (InfoPtr < BufferPtr + BufferBytes)
					{
						PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX ProcessorInfo = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) InfoPtr;

						if (nullptr == ProcessorInfo)
						{
							break;
						}

						if (ProcessorInfo->Relationship == RelationProcessorCore)
						{
							if (bForceSingleNumaNode)
							{
								for (int GroupIdx = 0; GroupIdx < ProcessorInfo->Processor.GroupCount; ++GroupIdx)
								{
									if (FilterGroupAffinity.Group == ProcessorInfo->Processor.GroupMask[GroupIdx].Group)
									{
										KAFFINITY Intersection = FilterGroupAffinity.Mask & ProcessorInfo->Processor.GroupMask[GroupIdx].Mask;

										if (Intersection > 0)
										{
											OutCoreCount++;

											OutLogicalProcessorCount += MathUtils::CountBits(Intersection);
										}
									}
								}
							}
							else
							{
								OutCoreCount++;

								for (int GroupIdx = 0; GroupIdx < ProcessorInfo->Processor.GroupCount; ++GroupIdx)
								{
									OutLogicalProcessorCount += MathUtils::CountBits(ProcessorInfo->Processor.GroupMask[GroupIdx].Mask);
								}
							}
						}
						if (ProcessorInfo->Relationship == RelationNumaNode)
						{
							OutNumaNodeCount++;
						}

						if (ProcessorInfo->Relationship == RelationGroup)
						{
							OutGroupDesc.NumProcessorGroups = MathUtils::Min<uint16>(FProcessorGroupDesc::MaxNumProcessorGroups, ProcessorInfo->Group.ActiveGroupCount);
							for(int32 GroupIndex = 0; GroupIndex < OutGroupDesc.NumProcessorGroups; GroupIndex++)
							{
								OutGroupDesc.ThreadAffinities[GroupIndex] = ProcessorInfo->Group.GroupInfo[GroupIndex].ActiveProcessorMask;
							}
						}

						InfoPtr += ProcessorInfo->Size;
					}
				}
				delete[] BufferPtr;
				//FMemory::Free(BufferPtr);
			}
		}
	}
    void WindowsPlatformMisc::SetThreadName(const char* name)
    {
        TCHAR Name[256]={0};
        StringUtils::ASCIIToUnicode(name,Name);
        {
            typedef HRESULT(WINAPI *SetThreadDescriptionFnPtr)(HANDLE hThread, PCWSTR lpThreadDescription);
#pragma warning( push )
#pragma warning( disable: 4191 )	// unsafe conversion from 'type of expression' to 'type required'
            static SetThreadDescriptionFnPtr RealSetThreadDescription = (SetThreadDescriptionFnPtr) GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "SetThreadDescription");
#pragma warning( pop )

            if (RealSetThreadDescription)
            {
                RealSetThreadDescription(::GetCurrentThread(), Name);
            }
        }
        {
#if !PLATFORM_SEH_EXCEPTIONS_DISABLED
            /**
            * Code setting the thread name for use in the debugger.
            *
            * http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx
            */
            const uint32 MS_VC_EXCEPTION=0x406D1388;

            struct THREADNAME_INFO
            {
                uint32 dwType;		// Must be 0x1000.
                LPCSTR szName;		// Pointer to name (in user addr space).
                uint32 dwThreadID;	// Thread ID (-1=caller thread).
                uint32 dwFlags;		// Reserved for future use, must be zero.
            };

            THREADNAME_INFO ThreadNameInfo;
            ThreadNameInfo.dwType		= 0x1000;
            ThreadNameInfo.szName		= name;
            ThreadNameInfo.dwThreadID	= ::GetCurrentThreadId();
            ThreadNameInfo.dwFlags		= 0;

            __try
            {
                RaiseException( MS_VC_EXCEPTION, 0, sizeof(ThreadNameInfo)/sizeof(ULONG_PTR), (ULONG_PTR*)&ThreadNameInfo );
            }
            __except( EXCEPTION_EXECUTE_HANDLER )
            {
            }
#endif
        }
    }
    GenericApplication* WindowsPlatformMisc::CreateApplication()
    {
        return WindowsApplication::CreateWindowsApplication(PlatformMisc::mInstance,nullptr);
    }
    int32 WindowsPlatformMisc::NumberOfCores()
    {
        static int32 CoreCount = 0;
        if (CoreCount > 0)
        {
            return CoreCount;
        }

        FProcessorGroupDesc GroupDesc;
        uint32 NumaNodeCount = 0;
        uint32 NumCores = 0;
        uint32 LogicalProcessorCount = 0;
        QueryCpuInformation(GroupDesc, NumaNodeCount, NumCores, LogicalProcessorCount);

        bool bLimitsInitialized;
        int32 PhysicalCoreLimit;
        int32 LogicalCoreLimit;
        bool bSetPhysicalCountToLogicalCount;
        GetConfiguredCoreLimits(NumCores, LogicalProcessorCount, bLimitsInitialized, PhysicalCoreLimit,
            LogicalCoreLimit, bSetPhysicalCountToLogicalCount);

        CoreCount = bSetPhysicalCountToLogicalCount ? LogicalProcessorCount : NumCores;

        // Optionally limit number of threads (we don't necessarily scale super well with very high core counts)
        if (PhysicalCoreLimit > 0)
        {
            CoreCount = MathUtils::Min(CoreCount, PhysicalCoreLimit);
        }

        return CoreCount;
    }
    int32 WindowsPlatformMisc::NumberOfCoresIncludingHyperthreads()
    {
        static int32 CoreCount = 0;
        if (CoreCount > 0)
        {
            return CoreCount;
        }

        FProcessorGroupDesc GroupDesc;
        uint32 NumaNodeCount = 0;
        uint32 NumCores = 0;
        uint32 LogicalProcessorCount = 0;
        QueryCpuInformation(GroupDesc, NumaNodeCount, NumCores, LogicalProcessorCount);

        bool bLimitsInitialized;
        int32 PhysicalCoreLimit;
        int32 LogicalCoreLimit;
        bool bSetPhysicalCountToLogicalCount;
        GetConfiguredCoreLimits(NumCores, LogicalProcessorCount, bLimitsInitialized, PhysicalCoreLimit,
            LogicalCoreLimit, bSetPhysicalCountToLogicalCount);

        CoreCount = LogicalProcessorCount;

        // Optionally limit number of threads (we don't necessarily scale super well with very high core counts)
        if (LogicalCoreLimit > 0)
        {
            CoreCount = MathUtils::Min(CoreCount, LogicalCoreLimit);
        }

        return CoreCount;
    }
    int32 WindowsPlatformMisc::NumberOfWorkerThreadsToSpawn()
    {	
        static int32 MaxServerWorkerThreads = 4;

        int32 MaxWorkerThreads = INT32_MAX;

        int32 NumberOfCores = WindowsPlatformMisc::NumberOfCores();
        int32 NumberOfCoresIncludingHyperthreads = WindowsPlatformMisc::NumberOfCoresIncludingHyperthreads();
        int32 NumberOfThreads = 0;

        if (NumberOfCoresIncludingHyperthreads > NumberOfCores)
        {
            NumberOfThreads = NumberOfCoresIncludingHyperthreads - 2;
        }
        else
        {
            NumberOfThreads = NumberOfCores - 1;
        }

        int32 MaxWorkerThreadsWanted = MaxWorkerThreads;//IsRunningDedicatedServer() ? MaxServerWorkerThreads : MaxWorkerThreads;
        // need to spawn at least one worker thread (see FTaskGraphImplementation)
        return MathUtils::Max(MathUtils::Min(NumberOfThreads, MaxWorkerThreadsWanted), 2);
    }
}